/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 huliqing, huliqing.cn@gmail.com
 *
 * This file is part of QFaces.
 * QFaces is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QFaces is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with QFaces.  If not, see <http://www.gnu.org/licenses/>.
 *
 * 这个文件是QFaces的一部分。
 * 您可以单独使用或分发这个文件，但请不要移除这个头部声明信息.
 * QFaces是一个自由软件，您可以自由分发、修改其中的源代码或者重新发布它，
 * 新的任何修改后的重新发布版必须同样在遵守LGPL3或更后续的版本协议下发布.
 * 关于LGPL协议的细则请参考COPYING、COPYING.LESSER文件，
 * 您可以在QFaces的相关目录中获得LGPL协议的副本，
 * 如果没有找到，请连接到 http://www.gnu.org/licenses/ 查看并获取副本。
 *
 * - Author: Huliqing
 * - Contact: huliqing.cn@gmail.com
 * - License: GNU Lesser General Public License (LGPL)
 * - Blog and source code availability: http://www.huliqing.name/
 */


package name.huliqing.qfaces;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.servlet.http.HttpServletResponse;

/**
 * 
 * @author huliqing
 */
public class ResLoader2 {

    private final static Logger logger =
            Logger.getLogger(ResLoader2.class.getName());
    private final static ResLoader2 ins = new ResLoader2();

    private ResLoader2() {
    }

    public final static ResLoader2 getInstance() {
        return ins;
    }

    public enum LoadType {

        /**
         * 积极的装载方式,该方式不会缓存数据，并且在组件渲染的同时立即输出
         * 资源,并且以直接的输出方式，这可以保证在任何情况下都不会造成资源
         * 丢失，或资源未能及时装载的问题,但是性能相对较差。
         */
        ENERGY,
        /**
         * 普通的装载方式，该方式在组件渲染的同时输出即输出资源，但是可能缓存数据于浏览器。
         * (除非在web.xml中指定develop为true)
         */
        GENERAL,
        /**
         * 懒惰装载方式,该方式与GENERAL方式相同，但是资源的输出将放在页面的最后。这不会造成
         * 资源在装载的同时阻塞页面其它元素的装载,可以带来最好的页面装载性能。
         * 需要注意的是：该方式资源的装载是不及时的，所以应该清楚页面中是否存在需要及时调用
         * 相应资源的操作或组件的存在。
         */
        LAZY;
    }
    // The container id of the QFaces resource.
    private String ID_RESOURCE_JS = "name.huliqing.qfaces.resource.js";

    /**
     * 加载系统必要的资源
     *
     * @param ui
     * @throws IOException
     */
    public final void loadRequired(UIComponent ui) throws IOException {
        if (ui == null) {
            throw new NullPointerException("UI not found!");
        }

        // Check load type/检查装载方式
        LoadType lt = checkLoadType(ui);

        // Init system resource/加载系统必要资源
        checkAndInitResourceObj();

        // Out put script if energy else ignore. 如果为积极装载方式,则立即检查并输出系统资源。
        if (lt == LoadType.ENERGY) {
            checkAndResponseSystemResourceAsEnergy();
        } else if (lt == LoadType.GENERAL) {
            checkAndResponseSystemResourceAsGeneral();
        }
        // lazy积极方式不需要处理
    }

    /**
     * 加载资源文件,路径类似: name/huliqing/qfaces/resource/xxx.js
     * @param ui
     * 		关联的UIComponent,不能为null
     * @param js
     * 		JavaScript 资源文件,如果为null,则直接返回
     * @throws IOException
     * @throws NullPointerException if ui not found.
     */
    public final void loadJavaScript(UIComponent ui, String js)
            throws IOException {

        if (ui == null) {
            throw new NullPointerException("UI not found!");
        }

        if (js == null || "".equals(js)) {
            return;
        }

        ResourceObj ro = checkAndInitResourceObj();
        LoadType lt = checkLoadType(ui);

        if (lt == LoadType.ENERGY) {
            // 如果资源并未输出,则输出资源并记住已经输出过该资源
            if (!ro.getJavaScriptLoaded().contains(js)) {
                // 记住已经输出该资源
                ro.addJavaScriptLoaded(js);
                // 输出资源,需要检查是否已经加载了系统必要的资源
                checkAndResponseSystemResourceAsEnergy();
                writeScriptAsEnergy(js);
            }
        } else if (lt == LoadType.GENERAL) {
            if (!ro.getJavaScriptLoaded().contains(js)) {
                ro.addJavaScriptLoaded(js);
                checkAndResponseSystemResourceAsGeneral();
                writeScriptAsGeneral(js);
            }
        } else if (lt == LoadType.LAZY) {
            // 记住该资源，但并不立即输出
            ro.addJavaScriptLazy(js);
        } else {
            logger.severe("Unknow loadType, loadType=" + lt);
        }
    }

    /**
     * 加载JavaScript init函数
     * @param ui
     * 		关联的UIComponent,不能为null
     * @param init
     * 		JavaScript 初始化函数,如果为null,则直接返回
     * @throws IOException
     * @throws NullPointerException if ui not found.
     */
    public final void loadJavaScriptInit(UIComponent ui, String init)
            throws IOException {

        if (ui == null) {
            throw new NullPointerException("UI not found!");
        }

        if (init == null || "".equals(init)) {
            return;
        }

        ResourceObj ro = this.checkAndInitResourceObj();
        LoadType lt = checkLoadType(ui);

        if (lt == LoadType.ENERGY) {
            checkAndResponseSystemResourceAsEnergy();
            writeScriptInitAsEnergy(init);
        } else if (lt == LoadType.GENERAL) {
            checkAndResponseSystemResourceAsGeneral();
            writeScriptInitAsGeneral(init);
        } else if (lt == LoadType.LAZY) {
            ro.addJavaScriptInitLazy(init);
        } else {
            logger.severe("Unknow loadType, loadType=" + lt);
        }
    }

    /**
     * 输出图片资源
     *
     * @param path
     *            图片路径,可能像这样: name/huliqing/qfaces/resource/QFaces.png
     * @throws java.io.IOException
     */
    public final void loadImage(String path) throws IOException {
        FacesContext fc = QFaces.getFacesContext();
        String contextPath = fc.getExternalContext().getRequestContextPath();
        ResponseWriter w = fc.getResponseWriter();
        w.startElement("img", null);
        w.writeAttribute("class", "_qf_non", null);
        w.writeAttribute("src", contextPath + Constant.PREFIX_RES_IMAGE + path
                + "." + Constant.SUFFIX, null);
        w.endElement("img");
    }

    /**
     * 将资源包里的图片路径转化为qfaces可以识别的路径
     *
     * @param path
     * @return full src
     */
    public final String makeImageSrc(String path) {
        String contextPath = QFaces.getFacesContext().getExternalContext().getRequestContextPath();
        return (contextPath + Constant.PREFIX_RES_IMAGE + path + "." + Constant.SUFFIX);
    }

    /**
     * 装载所有延迟加载的JavaScript资源及init函数
     * @throws IOException
     */
    public final void responseAllLazyJavaScript() throws IOException {
        ResourceObj resObj = this.checkAndInitResourceObj();
        // 确保系统资源优先加载
        checkAndResponseSystemResourceAsLazy();

        // 装载延迟加载的JS资源文件
        ArrayList<String> jss = resObj.getJavaScriptLazy();
        ArrayList<String> inits = resObj.getJavaScriptInitLazy();
        for (String js : jss) {
            writeScriptAsLazy(js);
        }
        for (String init : inits) {
            writeScriptInitAsLazy(init);
        }
    }

    // ---------------------------------------------------------------------- Protected
    public final void checkAndResponseSystemResourceAsEnergy() throws IOException {
        ResourceObj resObj = checkAndInitResourceObj();
        if (!resObj.isSysLoaded()) {
            resObj.setSysLoaded(true);
            logger.info("Response system resource as energy start...");
            ArrayList<String> css = resObj.getSystemCss();
            ArrayList<String> js = resObj.getSystemJavaScript();
            ArrayList<String> jsInit = resObj.getSystemJavaScriptInit();
            for (String c : css) {
                writeCssAsEnergy(c);
                logger.info("Response css ok. css=" + c);
            }
            for (String j : js) {
                writeScriptAsEnergy(j);
                logger.info("Response javascript ok. js=" + j);
            }
            for (String ji : jsInit) {
                writeScriptInitAsEnergy(ji);
                logger.info("Response js init ok. init=" + ji);
            }
            logger.info("Response system resource as energy OK.");
        }
    }

    public final void checkAndResponseSystemResourceAsGeneral() throws IOException {
        // Same as lazy
        checkAndResponseSystemResourceAsLazy();
    }

    public final void checkAndResponseSystemResourceAsLazy() throws IOException {
        ResourceObj resObj = checkAndInitResourceObj();
        if (!resObj.isSysLoaded()) {
            resObj.setSysLoaded(true);
            logger.info("Response system resource as lazy start...");
            ArrayList<String> css = resObj.getSystemCss();
            ArrayList<String> js = resObj.getSystemJavaScript();
            ArrayList<String> jsInit = resObj.getSystemJavaScriptInit();
            for (String c : css) {
                writeCssAsLazy(c);
                logger.info("Response css ok. css=" + c);
            }
            for (String j : js) {
                writeScriptAsLazy(j);
                logger.info("Response javascript ok. js=" + j);
            }
            for (String ji : jsInit) {
                writeScriptInitAsLazy(ji);
                logger.info("Response js init ok. init=" + ji);
            }
            logger.info("Response system resource as lazy OK.");
        }
    }

    // -------------------------------------------------- I'm Separator
    // 查找并生成资源容器,同时替换ResponseWriter
    private final ResourceObj checkAndInitResourceObj() throws IOException {
        FacesContext fc = QFaces.getFacesContext();
        Map<String, Object> rMap = fc.getExternalContext().getRequestMap();
        ResourceObj resObj = (ResourceObj) rMap.get(ID_RESOURCE_JS);
        if (resObj == null) {
            resObj = new ResourceObj();
            rMap.put(ID_RESOURCE_JS, resObj);

            // 加入系统必要的资源
            resObj.addSystemCss("style.css");
            resObj.addSystemJavaScript("QFaces-Utils.js");
            resObj.addSystemJavaScript("QFaces-Faces.js");

            // 必要的初始化信息
            String contextPath = fc.getExternalContext().getRequestContextPath();
            StringBuilder sb = new StringBuilder().append("Q.F.contextPath = \"" + contextPath + "\";").append("Q.F.viewId = \"" + fc.getViewRoot().getViewId() + "\";").append("Q.F.suffix = \"" + Constant.SUFFIX + "\";").append("Q.F.filterName = \"" + Constant.FILTER_NAME + "\";").append("Q.F.loadingSrc = \"" + makeImageSrc(Constant.QFACES_LOADING) + "\";");
            resObj.addSystemJavaScriptInit(sb.toString());

            // Handle the ResponseWriter.
            // Replace ResponseWriter with MyResponseWriter
            // MyResponseWriter will used when LoadType = Lazy.
            ResponseWriter rw = fc.getResponseWriter();
            if (rw instanceof name.huliqing.qfaces.MyResponseWriter) {
                // Ignore.RW非单例，正常应该不会是入这里。
            } else {
                MyResponseWriter mrw = new MyResponseWriter(rw);
                fc.setResponseWriter(mrw);
            }
        }
        return resObj;
    }

    private void writeScriptAsEnergy(String path) {
        String fullPath = this.getFullPath(path);
        FacesContext fc = QFaces.getFacesContext();
        HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();
        // 确保输出编码正确
        response.setCharacterEncoding("utf-8");

        ResponseWriter rw = fc.getResponseWriter();
        try {
//			long start = System.currentTimeMillis();
            URL url = getClass().getClassLoader().getResource(fullPath);
            InputStream is = url.openStream();
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            BufferedReader br = new BufferedReader(isr);
            rw.startElement("script", null);
            rw.writeAttribute("type", "text/javascript", null);
            rw.writeAttribute("language", "javascript", null);

            char[] buff = new char[1024];
            int len;
            while ((len = br.read(buff)) != -1) {
                rw.write(buff, 0, len);
            }
            rw.endElement("script");
            br.close();
//			long end = System.currentTimeMillis();
//			System.out.println("Write Script " + path + ", use time=" + (end - start));
            return;
        } catch (IOException ioe) {
            logger.severe("Couldn't load javascript. Make sure your file"
                    + "path like this \"name/huliqing/qfaces/resource/somejs.js\","
                    + "and not start width \"/\"");
            logger.log(Level.SEVERE, ioe.getMessage(), ioe);
        }
    }

    private void writeScriptAsGeneral(String path) throws IOException {
        // Same as lazy
        writeScriptAsLazy(path);
    }

    private void writeScriptAsLazy(String path) throws IOException {
        FacesContext fc = QFaces.getFacesContext();
        String contextPath = fc.getExternalContext().getRequestContextPath();
        ResponseWriter w = fc.getResponseWriter();
        w.startElement("script", null);
        w.writeAttribute("type", "text/javascript", null);
        w.writeAttribute("src",
                contextPath
                + Constant.PREFIX_RES_JAVASCRIPT
                + path + "." + Constant.SUFFIX, null);
        w.endElement("script");
    }

    private void writeScriptInitAsEnergy(String init) throws IOException {
        // Same the lazy
        writeScriptInitAsLazy(init);
    }

    private void writeScriptInitAsGeneral(String init) throws IOException {
        // Same the lazy
        writeScriptInitAsLazy(init);
    }

    private void writeScriptInitAsLazy(String init) throws IOException {
        ResponseWriter w = QFaces.getFacesContext().getResponseWriter();
        w.startElement("script", null);
        w.writeAttribute("type", "text/javascript", null);
        w.writeText(init, null);
        w.endElement("script");
    }

    private final void writeCssAsEnergy(String path) throws IOException {
        String fullPath = this.getFullPath(path);
        FacesContext fc = QFaces.getFacesContext();
        HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();
        // 确保输出编码正确
        response.setCharacterEncoding("utf-8");

        ResponseWriter rw = fc.getResponseWriter();
        try {
            URL url = getClass().getClassLoader().getResource(fullPath);
            InputStream is = url.openStream();
            InputStreamReader isr = new InputStreamReader(is, "utf-8"); // utf-8必要的
            BufferedReader br = new BufferedReader(isr);
            rw.startElement("style", null);
            rw.writeAttribute("type", "text/css", null);
            char[] buff = new char[1024];
            int len;
            while ((len = br.read(buff)) != -1) {
                rw.write(buff, 0, len);
            }
            rw.endElement("style");
            br.close();
        } catch (IOException ioe) {
            logger.log(Level.SEVERE, ioe.getMessage(), ioe);
        }
    }

    private final void writeCssAsLazy(String path) throws IOException {
        FacesContext fc = QFaces.getFacesContext();
        String contextPath = fc.getExternalContext().getRequestContextPath();
        ResponseWriter w = fc.getResponseWriter();
        w.startElement("link", null);
        w.writeAttribute("type", "text/css", null);
        w.writeAttribute("rel", "stylesheet", null);
        w.writeAttribute("href", contextPath + Constant.PREFIX_RES_CSS + path
                + "." + Constant.SUFFIX, null);
        w.endElement("link");
    }

    // ---------------------------------------------------------------------
    /**
     * 获取全路径的资源文件,
     * @param shortPath
     * 		shortPath可能没有包含路径 "name/huliqing/qfaces/resource/",
     * 		所以应该检查，并返回正确路径
     * @return
     */
    private String getFullPath(String shortPath) {
        String fullPath = null;
        if (!shortPath.contains("/")) {
            fullPath = Constant.QFACES_DIR_RESOURCE + shortPath;
        } else {
            fullPath = shortPath;
        }
        return fullPath;
    }

    /**
     * 查找目标组件的资源装载方式
     * @param ui
     * @return
     */
    private LoadType checkLoadType(UIComponent ui) {
        String lt = (String) ui.getAttributes().get("loadType");
        Boolean lazy = QFaces.convertToBoolean(ui.getAttributes().get("lazyLoad"));
        // 默认装载类型
        LoadType def = LoadType.GENERAL;
        LoadType type = null;
        // 优先从loadType中查找属性
        if (lt != null) {
            lt = lt.toUpperCase();// 确保为大写，小写会匹配错误
            try {
                type = LoadType.valueOf(lt);
            } catch (Exception e) {
                logger.warning("LoadType unsupported, loadType=" + lt);
            }
        }
        // 其次从lazyLoad属性中查找，兼容前版本
        if (type == null) {
            if (lazy != null) {
                type = lazy ? LoadType.LAZY : LoadType.ENERGY;
            }
        }
        if (type == null) {
            type = def;
        }
        return type;
    }
}
